#include "ExtensibleHash.h"

ExtensibleHash::ExtensibleHash(string name) {
	Configuration* config = Configuration::getInstance();
    string path = config->getHashPath() + "hash" + name;
	this->blockFile = new BlockFileManager(config->getBlockSize(), path);
	string tablePath = config->getHashPath() + name + "hashTable.dat";
	this->hashTable.open(tablePath.c_str(), ios::in | ios::out| ios::binary);
	if (!(this->hashTable.good())) {
		this->hashTable.open(tablePath.c_str(), ios::in | ios::out | ios::trunc | ios::binary);
		this->table = new int[1];
		this->tableSize = 1;
		Bucket *bucket = new Bucket(this->getTableSize(),this->blockFile->getBlockSize()); //con td: 1 y bytes libres (blocksize-sizeoof(INT)*2)
		int n = this->blockFile->writeNewBlock(bucket->getBlock());
		this->table[0] = n;//this->blockFile->writeNewBlock(bucket->getBlock());
		this->writeTableFile();
	} else {
		this->readTableFile();
	}
}

int ExtensibleHash::applyHashFuction(string key, int td) {
	int lon = key.length();
	int disper = 0;
	int keyNumber = 0;
	for (int i = 1; i < lon; i++) {
		    keyNumber += key[i];
	}
	disper = keyNumber%td;
	return disper;
}

void ExtensibleHash::writeTableFile(){
	this->hashTable.seekp(0,ios::beg);
	this->hashTable.write((char*) &(this->tableSize), sizeof(int));
	this->hashTable.write((char*) this->table, ((this->tableSize)*sizeof(int)));
//		this->hashTable.clear();
	this->hashTable.clear();

}

void ExtensibleHash::readTableFile(){
	this->hashTable.seekg(0,ios::beg);
	int tablS = 0;
	this->hashTable.read((char*) &tablS, sizeof(int));
	this->tableSize = tablS;
	this->table = new int[this->tableSize];
	this->hashTable.read((char*) this->table, ((this->tableSize)*sizeof(int)));
	this->hashTable.clear();
}
void ExtensibleHash::insertRecord(Record *newRecord) {
	int tablePosition = this->applyHashFuction(newRecord->getKey(),this->getTableSize());
	int blockNumber = this->table[tablePosition];
	char* block = this->blockFile->readBlock(blockNumber);
	Bucket* bucket = new Bucket(block, this->blockFile->getBlockSize());
	estados result = bucket->insertRecord(newRecord);
	switch (result) {
	case (ok):
		this->blockFile->updateBlock(bucket->getBlock(), blockNumber);
		break;
	case (full):
		/*no pudo insertar porque ya estaba lleno el bloque
		 *chequear que el td del bloque sea mayor o igual que el de la tabala para hacer esto */
		if ((bucket->getDispersionSize()) >= (this->getTableSize())) {
			this->duplicateTable();
			int tableSize = this->getTableSize();
			Bucket *newBucket = new Bucket(tableSize,this->blockFile->getBlockSize());
			char * newBlock = newBucket->getBlock();
			int newBlockNumber = this->blockFile->writeNewBlock(newBlock);
			this->table[tablePosition] = newBlockNumber;
			this->blockFile->updateBlock(newBlock, blockNumber);
			this->reDisperse(bucket->getRecords(), newRecord);
			delete newBucket;
		} else {
			/*se cambia el tamaño de la dispersion del bloque desbordado y se redispersan sus registros
			 *se vuelve a intentar insertar el reg que no entraba*/
			Bucket *newBucket = new Bucket((bucket->getDispersionSize()* 2),this->blockFile->getBlockSize());
			int newBlockNumber = this->blockFile->writeNewBlock(newBucket->getBlock());
			this->blockFile->updateBlock(newBucket->getBlock(), blockNumber);
			this->updateTable(tablePosition, newBucket->getDispersionSize(),newBlockNumber);
			this->reDisperse(bucket->getRecords(), newRecord);
			delete newBucket;
		}
		break;
	}
	delete bucket;
}

void ExtensibleHash::reDisperse(vector<Record*>* records, Record* newRecord) {
	for (unsigned int i = 0; i < records->size(); i++) {
		//Record record = records->at(i);
		//string recordKey = record.getKey();
		this->insertRecord((records->at(i)));

	}
	//trying to insert new record
	this->insertRecord(newRecord);
}


Record * ExtensibleHash::searchRecord(string key) {
	int tablePosition = this->applyHashFuction(key, this->getTableSize());
	int blockNumber = this->table[tablePosition];
	char * block = this->blockFile->readBlock(blockNumber);
	Bucket* bucket = new Bucket(block, this->blockFile->getBlockSize());
	Record* result = bucket->getRecords()->at(bucket->searchRecord(key));
	return result;
}

int ExtensibleHash::getPositionNewBlock() {
	if (this->getTableSize() == 1) {
		return 0;
	} else {
		int result = 1;
		int cociente = this->getTableSize() / 2;
		while (cociente != 1) {
			cociente = cociente / 2;
			result++;
		}
		return result;
	}
}

void ExtensibleHash::duplicateTable() {
	int *newTable = new int[(this->getTableSize() * 2)];
	int k = 0;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < (this->getTableSize()); j++) {

			newTable[j + k] = this->table[j];
		}
		k = this->getTableSize();
	}
	//guardo la nueva tabla lo puedo poner un metodo
	delete table;
	this->table = newTable;
	this->tableSize = (this->tableSize * 2);
}

/*
 * retorna numero de bloque si es posible liberlarlo, de otro modo retorna -1
 */
int ExtensibleHash::canReleaseBlock(int position, int td) {
	int mov = td / 2;
	int result = -1;
	int up = position + mov;
	if (up >= this->getTableSize()) {//TODO::VEEER ESTO PLEASE
		up = up - this->getTableSize();
	}
	int down = position - mov;
	if (down < 0) {
		down = down + this->getTableSize();
	}
	if ((this->table[up] == this->table[down]) && (td != 1)) {
		result = this->table[up];
	}
	return result;
}

void ExtensibleHash::updateTable(int position, int td, int blockNumber) {
	int mov = td;
	this->table[position] = blockNumber;
	int up = position + mov;
	if (up >= this->getTableSize()) {
		up = up - this->getTableSize();
	}
	while (up != position) {
		this->table[up] = blockNumber;
		up = up + mov;
		if (up > this->getTableSize()) {
			up = up - this->getTableSize();
		}
	}
	//TODO:: ACTUALIZAR EL ARCHIVO !
     this->writeTableFile();
}
void ExtensibleHash::deleteRecord(Record* record) {

	int tablePosition = this->applyHashFuction(record->getKey(),this->getTableSize());
	int blockNumber = this->table[tablePosition];
	char* block = this->blockFile->readBlock(blockNumber);
	Bucket* bucket = new Bucket(block, this->blockFile->getBlockSize());
	estados result = bucket->deleteRecord(record);
	switch (result){
	case (ok):
		this->blockFile->updateBlock(bucket->getBlock(), blockNumber);
		delete bucket;
		break;
	case (empty):{
		int replacementBucketNumber = this->canReleaseBlock(tablePosition,bucket->getDispersionSize());
		if (replacementBucketNumber != -1) {
			this->blockFile->deleteBlock(blockNumber);//  el bloque que quedo vacio
			char * block = this->blockFile->readBlock(replacementBucketNumber);
			Bucket* replacementBucket = new Bucket(block,this->blockFile->getBlockSize());
			this->updateTable(tablePosition,replacementBucket->getDispersionSize(),replacementBucketNumber); //todo PINCHAAAA !!!!!
			replacementBucket->setDispersionSize(replacementBucket->getDispersionSize() / 2);
			this->blockFile->updateBlock(replacementBucket->getBlock(),replacementBucketNumber);
			this->truncateTable();//lo hace si es posible, si no hace nada
			delete replacementBucket;
		}else {
			//if (this->getTableSize()==1){//TODO
			this->blockFile->updateBlock(bucket->getBlock(), blockNumber);
			//}
		}
		delete bucket;
		break;
	}
	case (noaction):
		break;
	}

}
void ExtensibleHash::truncateTable() {
	int halfSize = (this->getTableSize() / 2);
	int* upHalf = this->table + halfSize;
	int* downHalf = this->table;
	bool equals = true;
	for (int i = 0; i < halfSize; i++) {
		if (downHalf[i] != upHalf[i]) {
			equals = false;
		}
	}
	if (equals && halfSize != 0) {
		int *newTable = new int[halfSize];
		for (int i = 0; i < halfSize; i++) {
			newTable[i] = this->table[i];
		}
		this->table = newTable; //TODO:que pasa con el new ? como muere ?
		this->tableSize = halfSize;
	}
		this->writeTableFile();
}

void ExtensibleHash::exportToTxt(string name) {
	fstream txtFile;
	Configuration* config = Configuration::getInstance();
	string fileName = config->getReportsFilesPath() + name +".  txt";
	txtFile.open(fileName.c_str(), ios::out | ios::app);
	if (txtFile.bad()) {
		txtFile.open(fileName.c_str(), ios::out);
	}
	txtFile << "Hash extensible - Estado actual" << endl;
	int cant = this->blockFile->getNumberOfBlocks();
	txtFile << " " << endl;
	txtFile << "Tabla" << endl;
	int w=  this->getTableSize();
	for (int i = 0; i < w; i++) {
		txtFile << this->table[i] << " ";
	}
	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "Bloques Libres : ";
	vector<int>* freeBlocks = this->blockFile->getVecFreeBlocks();
	unsigned int ma = freeBlocks->size();
	for(unsigned int i= 0; i<freeBlocks->size();i++){
		txtFile<<" ("<<freeBlocks->at(i)<<") ";
	}
	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "Bloques : "<<endl;
	txtFile << endl;
	for (int i = 0; i < cant; i++) {
		char* block = this->blockFile->readBlock(i);
		if (block != NULL) {
			Bucket* bucket = new Bucket(block, this->blockFile->getBlockSize());
			txtFile << "(" << i << ") (" << bucket->getDispersionSize()
					<< ") (" << bucket->getFreeBytes() << ") ";
			vector<Record*> * records = bucket->getRecords();
			for (unsigned int i = 0; i < records->size(); i++) {
				txtFile << endl << "Clave: " << records->at(i)->getKey() << " "
						<< endl;
				txtFile << "Offsets: ";
				for (unsigned int j = 0; j < records->at(i)->getData()->size(); j++) {
					txtFile << records->at(i)->getData()->at(j) << " | ";
				}
			}
			txtFile << " " << endl << " * " << endl;
			delete bucket;
		}
	}
	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "**********" << endl;
	txtFile.close();
}

ExtensibleHash::~ExtensibleHash() {
	delete this->blockFile;
    this->writeTableFile();
	this->hashTable.close();
}
int ExtensibleHash::getTableSize() {
	return (this->tableSize);
}
